package cn.accjiyun.ca.security;

import cn.accjiyun.ca.common.constants.CommonConstants;
import cn.accjiyun.ca.common.utils.DateUtils;
import cn.accjiyun.ca.entity.CAConfig;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.x509.X509V3CertificateGenerator;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Date;

public class RSAUtils {

    public static void main(String[] args) {

        String path = "d:/test/";
        //证书属性类
        CAConfig caConfig = new CAConfig();
        //生成证书
        new BaseCert().generateCert(path + "student", caConfig);
        readCer(path + "student.cer");
        readPKCS12(path + "student.pfx", caConfig);

        //获取公钥
        try {
            //证书工厂
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            //读取公共证书文件
            FileInputStream fileInputstream = new FileInputStream(path + "student.cer");
            //载入证书文件
            X509Certificate x509certificate = (X509Certificate) certificateFactory.generateCertificate(fileInputstream);
            //读取公钥
            PublicKey publicKey = x509certificate.getPublicKey();
            //从KeyStore中获取PKCS12实例
            KeyStore keyStore = KeyStore.getInstance(CAConfig.PKCS12);
            //读取私有证书文件
            fileInputstream = new FileInputStream(path + "student.pfx");
            keyStore.load(fileInputstream, caConfig.getPK_PASSWORD().toCharArray());
            fileInputstream.close();
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(caConfig.getPK_ALIAS(), caConfig.getPK_PASSWORD().toCharArray());
            //私钥加密 公钥解密
            //获得摘要
            byte[] source = MdigestSHA("假户数据");
            //使用私钥对摘要进行加密 获得密文 即数字签名
            byte[] sign = sign(privateKey.getEncoded(), source);

            //使用公钥对密文进行解密,解密后与摘要进行匹配
            boolean isValid = verify(publicKey.getEncoded(), source, sign);
            if (isValid) {
                System.out.println("匹配成功 合法的签名!");
            }
            //公钥加密 私钥解密
            source = MdigestSHA("假户数据");
            sign = encryptByRSA(publicKey.getEncoded(), source);
            if (Arrays.equals(source, decryptByRSAPrivateKey(privateKey.getEncoded(), sign))) {
                System.out.println("匹配成功");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取cer证书
     *
     * @param certPath cer证书路径
     */
    public static PublicKey readCer(String certPath) {
        PublicKey publicKey = null;
        try {
            //证书工厂
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            //读取公共证书文件
            FileInputStream fileInputStream = new FileInputStream(certPath);
            //载入证书文件
            X509Certificate x509certificate = (X509Certificate) certificateFactory.generateCertificate(fileInputStream);
            //类型
            String type = x509certificate.getType();
            //版本
            String version = x509certificate.getVersion() + "";
            //标题
            String title = x509certificate.getSubjectDN().getName();
            //得到开始有效日期
            String notBefore = x509certificate.getNotBefore().toString();
            //得到截止日期
            String notAfter = x509certificate.getNotAfter().toString();
            //得到序列号
            String serialNumber = x509certificate.getSerialNumber().toString(16);
            //得到发行者名
            String issuerDN = x509certificate.getIssuerDN().getName();
            //得到签名算法
            String sigAlgName = x509certificate.getSigAlgName();
            //得到公钥算法
            String algorithm = x509certificate.getPublicKey().getAlgorithm();
            //获取公钥
            publicKey = x509certificate.getPublicKey();

            System.out.println("public key:" + new String(Base64.encodeBase64(x509certificate.getPublicKey().getEncoded())));
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return publicKey;
    }

    /**
     * 读取pfx证书
     *
     * @param path pfx证书路径
     */
    public static PrivateKey readPKCS12(String path, CAConfig caConfig) {
        PrivateKey privateKey = null;
        try {
            //中获取PKCS12 KeyStore实例
            KeyStore keyStore = KeyStore.getInstance(CAConfig.PKCS12);
            //读取私有证书文件
            FileInputStream fileInputStream = new FileInputStream(path);
            //获取密钥密码
            char[] password = caConfig.getPK_PASSWORD().toCharArray();
            //读取私钥
            keyStore.load(fileInputStream, password);
            //关闭文件输入流
            fileInputStream.close();
            //读取私钥
            privateKey = (PrivateKey) keyStore.getKey(caConfig.getPK_ALIAS(), password);
            //获取公钥
            Certificate certificate = keyStore.getCertificate(caConfig.getPK_ALIAS());
            PublicKey publicKey = certificate.getPublicKey();

            System.out.println("public key=" + new String(Base64.encodeBase64(publicKey.getEncoded())));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    /**
     * 使用RSA公钥加密数据
     *
     * @param pubKeyInByte 打包的byte[]形式公钥
     * @param data         要加密的数据
     * @return 加密数据
     */
    public static byte[] encryptByRSA(byte[] pubKeyInByte, byte[] data) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubKeyInByte);
            PublicKey publicKey = keyFactory.generatePublic(pubSpec);
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 使用RSA私钥加密数据
     *
     * @param privKeyInByte 打包的byte[]形式私钥
     * @param data          要加密的数据
     * @return 加密数据
     */
    public static byte[] encryptByRSA1(byte[] privKeyInByte, byte[] data) {
        try {
            PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(
                    privKeyInByte);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(privSpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 用RSA公钥解密
     *
     * @param publicKeyInByte 公钥打包成byte[]形式
     * @param data            要解密的数据
     * @return 解密数据
     */
    public static byte[] decryptByRSAPublicKey(byte[] publicKeyInByte, byte[] data) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(publicKeyInByte);
            PublicKey pubKey = keyFactory.generatePublic(pubSpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, pubKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 用RSA私钥解密
     *
     * @param privateKeyInByte 私钥打包成byte[]形式
     * @param data             要解密的数据
     * @return 解密数据
     */
    public static byte[] decryptByRSAPrivateKey(byte[] privateKeyInByte, byte[] data) {
        try {
            PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(
                    privateKeyInByte);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(privSpec);
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            return null;
        }
    }

    public static void outputFileForDownload(String username, HttpServletResponse response, CAConfig caConfig) {
        OutputStream os = null;
        OutputStream os2 = null;
        X509Certificate cert = null;
        try {
            KeyPair keyPair = new BaseCert().kpg.generateKeyPair();
            // 公钥
            PublicKey pubKey = keyPair.getPublic();
            // 私钥
            PrivateKey priKey = keyPair.getPrivate();
            X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
            // 设置序列号
            certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
            // 设置颁发者
            certGen.setIssuerDN(new X500Principal(CAConfig.CA_ROOT_ISSUER));
            // 设置有效期
            certGen.setNotBefore(new Date());
            certGen.setNotAfter(DateUtils.addMonth(new Date(), 12));
            // 设置使用者
            certGen.setSubjectDN(new X500Principal(caConfig.getCA_DEFAULT_SUBJECT() + username));
            // 公钥
            certGen.setPublicKey(pubKey);
            // 签名算法
            certGen.setSignatureAlgorithm(CAConfig.CA_ALGORITHM);
            cert = certGen.generateX509Certificate(priKey, "BC");

            // 导出为 cer 证书
            response.setContentType("application/x-x509-ca-cert;charset=utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + username + "_public.cer");
            os = response.getOutputStream();
            os.write(cert.getEncoded());
            os.flush();
            os.close();

            // 创建KeyStore
            KeyStore store = KeyStore.getInstance(CAConfig.PKCS12);
            store.load(null, null);
            store.setKeyEntry(caConfig.getPK_ALIAS(), priKey, caConfig.getPK_PASSWORD().toCharArray(), new Certificate[]{cert});
            store.store(os, caConfig.getPK_PASSWORD().toCharArray());
            // 导出为 pfx 证书
            response.setContentType("application/x-pkcs12;charset=utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + username + "_private.pfx");
            os2 = response.getOutputStream();
            os2.write(cert.getEncoded());
            os2.flush();
            os2.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * SHA摘要
     *
     * @param source
     * @return
     */
    public static byte[] MdigestSHA(String source) {
        try {
            MessageDigest thisMD = MessageDigest.getInstance(CAConfig.HASH);
            byte[] digest = thisMD.digest(source.getBytes("UTF-8"));
            return digest;
        } catch (Exception e) {
            return null;
        }
    }

    public static byte[] MdigestSHA(byte[] source) {
        try {
            MessageDigest thisMD = MessageDigest.getInstance(CAConfig.HASH);
            byte[] digest = thisMD.digest(source);
            return digest;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 使用私钥加密数据
     * 用一个已打包成byte[]形式的私钥加密数据，即数字签名
     *
     * @param privateKeyInByte 打包成byte[]的私钥
     * @param source           要签名的数据，一般应是数字摘要
     * @return 签名 byte[]
     */
    public static byte[] sign(byte[] privateKeyInByte, byte[] source) {
        try {
            PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyInByte);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);
            Signature signature = Signature.getInstance(CAConfig.SHA1WITHRSA);
            signature.initSign(privateKey);
            signature.update(source);
            return signature.sign();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 验证签名
     *
     * @param privateKeyInByte 二进制私钥
     * @param source           源数据
     * @param sign             数字签名
     * @return 验证结果
     */
    public static boolean verify(byte[] privateKeyInByte, byte[] source, byte[] sign) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(CAConfig.ALGORITHM);
            Signature signature = Signature.getInstance(CAConfig.SHA1WITHRSA);
            X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(privateKeyInByte);
            PublicKey publicKey = keyFactory.generatePublic(pubSpec);
            signature.initVerify(publicKey);
            signature.update(source);
            return signature.verify(sign);
        } catch (Exception e) {
            return false;
        }
    }

}